/**
 * 
 */
package de.cbf.cam.dsa.tavern.carte.model.entities;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import de.cbf.cam.dsa.tavern.carte.model.entities.interfaces.IEditableEntity;

/**
 * @author Stefan Alter
 *
 */
public class Ingredient extends Addendum implements IEditableEntity{
	public static final int TYPE_Category	    = 1<<1;
	public static final int TYPE_Ingredient     = 1<<2;
		
	public static final int PCE_INDEX_parent				=  4;
	public static final int PCE_INDEX_chosenProcessings		=  5;	
	public static final int PCE_INDEX_possibleProcessings	=  6;
	public static final int PCE_INDEX_countTypes			=  7;
	public static final int PCE_INDEX_subIngredients		=  8;
	public static final int PCE_INDEX_categoryTypes			=  9;
	public static final int PCE_INDEX_type					= 10;
		
	private int 			 	type 					= 0;
	private List<Processing> 	possibleProcessings 	= new ArrayList<Processing>();
	private List<Processing> 	chosenProcessings 		= new ArrayList<Processing>();
	private List<String> 	 	countTypes 				= new ArrayList<String>();
	private Collection<String> 	categoryTypes 			= new ArrayList<String>();
	
	private Ingredient		 	parent					= null;
	private List<Ingredient> 	subIngredients	 		= new ArrayList<Ingredient>();
	
	public Ingredient() {
		super();		
	}	
	
	public static Collection<Object> transformIngredientIterator(Iterator<Ingredient> iterator){
		Collection<Object> resultList = new ArrayList<Object>();		
		while (iterator.hasNext()) {
			Ingredient ingredient = (Ingredient) iterator.next();
			resultList.add(ingredient);
		}
		return resultList;	
	}
	@Override
	public Ingredient clone() {
		Ingredient clone = new Ingredient();
		clone.name 			 = name;
		clone.description 	 = description;
		clone.basePrice 	 = basePrice;
		clone.modifiedPrice  = modifiedPrice;
		clone.type			 = type;		
		clone.parent		 = parent;
		clone.subIngredients.addAll(subIngredients);
		clone.possibleProcessings	.addAll(possibleProcessings);		
		clone.chosenProcessings		.addAll(chosenProcessings);
		clone.countTypes			.addAll(countTypes);
		clone.categoryTypes			.addAll(categoryTypes);
		
		return clone;
	}
	@Override
	public void takeOverValuesFrom(IEditableEntity entity)
			throws IllegalArgumentException {
		if (entity instanceof Ingredient) {
			Ingredient ingredient = (Ingredient) entity;
			name			= ingredient.name;
			description		= ingredient.description;
			basePrice	 	= ingredient.basePrice;
			modifiedPrice	= ingredient.modifiedPrice;
			type			= ingredient.type;		
			parent			= ingredient.parent;
			subIngredients	= ingredient.subIngredients;
			possibleProcessings.clear();
			possibleProcessings.addAll(possibleProcessings);
			chosenProcessings.clear();
			chosenProcessings.addAll(chosenProcessings);
			countTypes.clear();
			countTypes.addAll(countTypes);
			categoryTypes.clear();
			categoryTypes.addAll(categoryTypes);
			propertyChangeSupport.firePropertyChange("all",null,this);
		}		
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((categoryTypes == null) ? 0 : categoryTypes.hashCode());
		result = prime
				* result
				+ ((chosenProcessings == null) ? 0 : chosenProcessings
						.hashCode());
		result = prime * result
				+ ((countTypes == null) ? 0 : countTypes.hashCode());		
		result = prime
				* result
				+ ((possibleProcessings == null) ? 0 : possibleProcessings
						.hashCode());
		result = prime * result + type;
		return result;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		if(! super.equals(obj)) return false;  //mine test
		Ingredient other = (Ingredient) obj;
		if (categoryTypes == null) {
			if (other.categoryTypes != null)
				return false;
		} else if (!categoryTypes.equals(other.categoryTypes))
			return false;
		if (chosenProcessings == null) {
			if (other.chosenProcessings != null)
				return false;
		} else if (!chosenProcessings.equals(other.chosenProcessings))
			return false;
		if (parent == null) {
			if (other.parent != null)
				return false;
		} else if (!parent.equals(other.parent))
			return false;
		if (countTypes == null) {
			if (other.countTypes != null)
				return false;
		} else if (!countTypes.equals(other.countTypes))
			return false;		
		if (possibleProcessings == null) {
			if (other.possibleProcessings != null)
				return false;
		} else if (!possibleProcessings.equals(other.possibleProcessings))
			return false;
		if (type != other.type)
			return false;
		return true;
	}

	@Override
	public PropertyChangeSupport getPropertyChangeSupport() {
		return propertyChangeSupport;
	}
	
	public Ingredient getMealIngredient(){
		Ingredient clone = new Ingredient();
		clone.name = name;
		clone.description 	= description;
		clone.basePrice = basePrice;
		clone.modifiedPrice = modifiedPrice;
		clone.type = type;
		clone.possibleProcessings.addAll(possibleProcessings);
		clone.chosenProcessings.addAll(chosenProcessings);
		clone.countTypes.addAll(countTypes);
		//parent/SubIngredients does not need to be cloned		
		return clone;
	}
		
	public int getType() {
		return type;
	}
	public void setType(int type) {
		if(this.type == type) return;
		int oldType = this.type;
		this.type = type;
		propertyChangeSupport.fireIndexedPropertyChange("type",PCE_INDEX_type, oldType,type);
	}	
	
	public Iterator<Processing> iteratorPossibleProcessings() {
		return possibleProcessings.iterator();
	}
	public void setPossibleProcessings(Collection<Processing> possibleProcessings) {
		Iterator<Processing> iterator = iteratorPossibleProcessings();
		while (iterator.hasNext()) {
			Processing processing = (Processing) iterator.next();
			processing.propertyChangeSupport.removePropertyChangeListener(pcaPossible);
		}
		this.possibleProcessings.clear();
		if(possibleProcessings == null){
			return;
		}
		boolean change = this.possibleProcessings.addAll(possibleProcessings);		
		if(change){
			iterator = iteratorPossibleProcessings();	
			while (iterator.hasNext()) {
				Processing processing = (Processing) iterator.next();
				processing.propertyChangeSupport.addPropertyChangeListener(pcaPossible);
			}
			propertyChangeSupport.fireIndexedPropertyChange(
					"possibleProcessings",PCE_INDEX_possibleProcessings, null,this.possibleProcessings);			
		}		
	}
	public boolean addPossibleProcessing(Processing processing){	
		boolean change = possibleProcessings.add(processing);
		if(change){
			processing.propertyChangeSupport.addPropertyChangeListener(pcaPossible);
			propertyChangeSupport.fireIndexedPropertyChange(
					"possibleProcessings",PCE_INDEX_possibleProcessings, null,possibleProcessings);
		}
		return change;		
	}
	public boolean removePossibleProcessing(Processing processing) {
		boolean change = possibleProcessings.remove(processing); 
		if(change){
			processing.propertyChangeSupport.removePropertyChangeListener(pcaPossible);
			propertyChangeSupport.fireIndexedPropertyChange(
					"possibleProcessings",PCE_INDEX_possibleProcessings, null,possibleProcessings);
		}
		return change;
	}	
		
	public Iterator<Processing> iteratorChosenProcessings() {
		return chosenProcessings.iterator();
	}
	public void setChosenProcessings(Collection<Processing> chosenProcessings) {
		Iterator<Processing> iterator = iteratorChosenProcessings();
		while (iterator.hasNext()) {
			Processing processing = (Processing) iterator.next();
			processing.propertyChangeSupport.removePropertyChangeListener(pcaChosen);
		}
		this.chosenProcessings.clear();
		if(chosenProcessings == null){
			return;
		}
		boolean change = this.chosenProcessings.addAll(chosenProcessings);		
		if(change){
			iterator = iteratorChosenProcessings();		
			while (iterator.hasNext()) {
				Processing processing = (Processing) iterator.next();
				processing.propertyChangeSupport.addPropertyChangeListener(pcaChosen);
			}
			propertyChangeSupport.fireIndexedPropertyChange(
					"chosenProcessings",PCE_INDEX_chosenProcessings, null,this.chosenProcessings);			
		}		
	}	
	
	/** checks if processing is possible
	 * @param processing
	 * @return 	true	: if added successfully
	 * 			false	: if not added, because not possible
	 */
	public boolean addChosenProcessing(Processing processing){
		if(possibleProcessings.contains(processing)){
			boolean change = chosenProcessings.add(processing);
			if(change){				
				processing.propertyChangeSupport.addPropertyChangeListener(pcaChosen);
				propertyChangeSupport.fireIndexedPropertyChange(
						"chosenProcessings",PCE_INDEX_chosenProcessings, null,chosenProcessings);				
			}			
			return change;
		}else{
			return false;
		}
	}
	public boolean removeChosenProcessing(Processing processing) {		
		boolean success = chosenProcessings.remove(processing);
		if(success){
			processing.propertyChangeSupport.removePropertyChangeListener(pcaChosen);			
			propertyChangeSupport.fireIndexedPropertyChange(
					"chosenProcessings",PCE_INDEX_chosenProcessings, null,chosenProcessings);		
		}
		return success;
	}	
	public int processingPosition(Processing processing){
		return chosenProcessings.indexOf(processing);
	}
	public void setProcessingToPosition(Processing processing,int position){
		Processing oldProcessing = chosenProcessings.set(position, processing);
		oldProcessing.propertyChangeSupport.removePropertyChangeListener(pcaChosen);
		processing.propertyChangeSupport.addPropertyChangeListener(pcaChosen);
		propertyChangeSupport.fireIndexedPropertyChange(
				"chosenProcessings",PCE_INDEX_chosenProcessings, null,chosenProcessings);
	}
	
	public String getProcessingsString() {
		StringBuffer buff = new StringBuffer();
		Iterator<Processing> iterator = iteratorChosenProcessings();
		while (iterator.hasNext()) {
			Processing processing = (Processing) iterator.next();
			buff.append(processing.getName());
			if(iterator.hasNext()){
				buff.append(", ");
			}
		}
		return buff.toString();
	}
	
	public Iterator<String> iteratorCountTypes() {
		return countTypes.iterator();
	}
	public void setCountTypes(Collection<String> countTypes) {
		this.countTypes.clear();
		boolean change = this.countTypes.addAll(countTypes); 
		if(change){
			propertyChangeSupport.fireIndexedPropertyChange(
					"countTypes",PCE_INDEX_countTypes, null,countTypes);
		}
	}
	public boolean addCountType(String type){
		boolean change = countTypes.add(type);		
		if(change){
			propertyChangeSupport.fireIndexedPropertyChange(
					"countTypes",PCE_INDEX_countTypes, null,countTypes);
		}
		return change;
	}
	public boolean removeCountType(String type){
		boolean change = countTypes.remove(type);		
		if(change){
			propertyChangeSupport.fireIndexedPropertyChange(
					"countTypes",PCE_INDEX_countTypes, null,countTypes);
		}
		return change;
	}
	/**
	 * needs to be public for XML-Im-/Export
	 * @return array of the sub ingredients
	 */
	public Ingredient[] getSubIngredientArray(){
		return subIngredients.toArray(new Ingredient[0]);		
	}	
	/**
	 * needs to be public for XML-Im-/Export
	 * @param subIngredients
	 */
	public void setSubIngredientArray(Ingredient[] subIngredients){
		List<Ingredient> newSubIngredients = new ArrayList<Ingredient>();
		for (int i = 0; i < subIngredients.length; i++) {
			newSubIngredients.add(subIngredients[i]);
		}
		setSubIngredients(newSubIngredients);
	}
	
	public Iterator<Ingredient> iteratorSubIngredients(){
		return subIngredients.iterator();
	}
	public boolean addSubIngredient(Ingredient ingredient){
		boolean change = subIngredients.add(ingredient);
		ingredient.setParent(this);
		if(change){
			propertyChangeSupport.fireIndexedPropertyChange(
					"subIngredients",PCE_INDEX_subIngredients, null,subIngredients);
		}
		return change;
	}
	public boolean removeSubIngredient(Ingredient ingredient){
		boolean change = subIngredients.remove(ingredient);
		if(change){
			propertyChangeSupport.fireIndexedPropertyChange(
					"subIngredients",PCE_INDEX_subIngredients, null,subIngredients);
		}
		return change;
	}
	public void setSubIngredients(List<Ingredient> subIngredients) {
		if(this.subIngredients.equals(subIngredients))return;
		this.subIngredients.clear();
		boolean change = this.subIngredients.addAll(subIngredients);
		for (Iterator<Ingredient> iterator = iteratorSubIngredients(); iterator.hasNext();) {
			Ingredient ingredient = (Ingredient) iterator.next();
			ingredient.setParent(this);
		}
		if(change){
			propertyChangeSupport.fireIndexedPropertyChange(
					"subIngredients",PCE_INDEX_subIngredients, null,subIngredients);
		}
	}
	public Ingredient getParent() {
		return parent;
	}
	protected void setParent(Ingredient parent) {		
		boolean change = true;
		if(this.parent != null && this.parent.equals(parent)){
			change = false;
		}		
		if(change){
			Ingredient oldParent = this.parent;
			this.parent = parent;
			propertyChangeSupport.fireIndexedPropertyChange(
					"parent",PCE_INDEX_parent, oldParent,parent);
		}		
	}
	
	public Iterator<String> iteratorCategoryTypes(){		
		return categoryTypes.iterator();
	}
	public boolean addCategoryType(String type){
		boolean change = categoryTypes.add(type);
		if(change){
			propertyChangeSupport.fireIndexedPropertyChange(
					"categoryType",PCE_INDEX_categoryTypes, null,categoryTypes);
		}
		return change;
	}
	public boolean removeCategoryType(String type){
		boolean change = categoryTypes.remove(type);
		if(change){
			propertyChangeSupport.fireIndexedPropertyChange(
					"categoryType",PCE_INDEX_categoryTypes, null,categoryTypes);
		}
		return change;
	}
	public void setCategoryTypes(Collection<String> categoryTypes) {
		if(this.categoryTypes.equals(categoryTypes)) return;
		this.categoryTypes.clear();
		boolean change = this.categoryTypes.addAll(categoryTypes);
		if(change){
			propertyChangeSupport.fireIndexedPropertyChange(
					"categoryType",PCE_INDEX_categoryTypes, null,categoryTypes);
		}
	}
	public boolean hasCategoryType(String type){
		return categoryTypes.contains(type);
	}
	
	public String getAddendumString() {
		String result = "";
		Iterator<Processing> iterator = iteratorChosenProcessings();
		while (iterator.hasNext()) {
			Processing processing = (Processing) iterator.next();
			Iterator<Addendum> iteratorAddendums = processing.iteratorAddendums();
			while (iteratorAddendums.hasNext()) {
				Addendum addendum = (Addendum) iteratorAddendums.next();
				result += addendum.getName()+", ";
			}			
		}
		if(result.length() < 2)return "";
		result = result.substring(0, result.length()-2);//omit last ", " 
		return result;
	}
	
	private PropertyChangeAdvancementChosen pcaChosen = new PropertyChangeAdvancementChosen();
	private class PropertyChangeAdvancementChosen implements PropertyChangeListener{
		@Override
		public void propertyChange(PropertyChangeEvent event) {
			propertyChangeSupport.fireIndexedPropertyChange(
					event.getPropertyName(),PCE_INDEX_chosenProcessings,null, chosenProcessings);
		}		
	}
	private PropertyChangeAdvancementPossible pcaPossible = new PropertyChangeAdvancementPossible();
	private class PropertyChangeAdvancementPossible implements PropertyChangeListener{
		@Override
		public void propertyChange(PropertyChangeEvent event) {
			propertyChangeSupport.fireIndexedPropertyChange(
					event.getPropertyName(),PCE_INDEX_possibleProcessings,null, possibleProcessings);
		}		
	}
	
	

	
		
}
